# imports
import pandas as pd
import numpy as np
import plotly.graph_objects as go
First, make sure market is exactly the same (i.e. has the same question with the same payout rules). Then, enter the Bovada odds as strings as the PredictIt odds as doubles and the two cells below will tell you whether arbitrage is possible in this market.
bovada_SIDE_A = "-750" #example: "-750"
bovada_SIDE_B = "+440" #example: "+440"
predictit_SIDE_A = .92 #example: .92
predictit_SIDE_B = .08 #example: .08
# move everything into decimal, it is more natural that way
# thus, make a function to convert Bovada to decimal
# PredictIt can stay as is
def Bovada_to_Decimal(odds_string):
if odds_string[0] == "-":
return round(int(odds_string[1:])/(int(odds_string[1:]) + 100), 4)
elif odds_string[0] == "+":
return round(100/(int(odds_string[1:]) + 100), 4)
else:
raise Exception("Make sure the Bovada odds were entered correctly.")
# initialize dictionaries with odds in same format
bovadaA_predictitB = {'bovada_SIDE_A': Bovada_to_Decimal(bovada_SIDE_A), 'predictit_SIDE_B': predictit_SIDE_B}
bovadaB_predictitA = {'bovada_SIDE_B': Bovada_to_Decimal(bovada_SIDE_B), 'predictit_SIDE_A': predictit_SIDE_A}
# check if arbitrage opportunity is available, and print opportunity if arbitrage opportunity is available
arbitrage_dict = {}
if sum(bovadaA_predictitB.values()) < 1:
# https://stackoverflow.com/questions/38987/how-do-i-merge-two-dictionaries-in-a-single-expression
arbitrage_dict = {**arbitrage_dict, **bovadaA_predictitB}
print('There is an arbitrage opportunity available between bovada_SIDE_A and predictit_SIDE_B.')
print('Pct Difference: ' + str(round(1 - sum(bovadaA_predictitB.values()), 4)))
if sum(bovadaB_predictitA.values()) < 1:
# https://stackoverflow.com/questions/38987/how-do-i-merge-two-dictionaries-in-a-single-expression
arbitrage_dict = {**arbitrage_dict, **bovadaB_predictitA}
print('There is an arbitrage opportunity available between bovada_SIDE_B and predictit_SIDE_A.')
print('Pct Difference: ' + str(round(1 - sum(bovadaB_predictitA.values()), 4)))
# quick check to make sure 4 keys weren't added to arbitrage dict,
# which would mean there is an arb opp available within a market (incredibly unlikely to happen)
if len(arbitrage_dict) > 2:
raise Exception('Check to make sure odds entered are correct.\
If odds are indeed correct, there is an arbitrage opportunity within one of the markets\
(incredibly unlikely to happen).')
# if no arbitrage opportunity, print no arbitrage opportunity
if len(arbitrage_dict) == 0:
print('There is no arbitrage opportunity available in this market currently.')
Enter the desired amount of money to put in, the number of intervals that the optimizer will use, and the method by which to calibrate expected profits (use Bovada odds only, use PredictIt odds only, or use a combination of the two). Then run the two cells below.
desired_money_in = 200
# enter an integer between 1 and 100*desired_money_in
# the calculations will get better as number_of_intervals approaches its max, but the running time will increase too
number_of_intervals = desired_money_in * 100
# enter "predictit_only", "bovada_only", or "combination"
calibration_method = 'combination'
# RAISE EXCEPTIONS IF THINGS DON'T MAKE SENSE
if len(arbitrage_dict) == 0:
raise Exception("No arbitrage opportunity is available. So this analysis will not make any sense.")
if not isinstance(number_of_intervals, int) or number_of_intervals < 1 or number_of_intervals > 100*desired_money_in:
raise Exception("Please enter an integer between 1 and " + str(100*desired_money_in) + " (100*desired_money_in).")
if calibration_method not in {"predictit_only", "bovada_only", "combination"}:
raise Exception("Please use one of the three valid calibration methods.")
# calculate range of possibilities to put into Bovada based on desired_money_in and number_of_intervals
money_to_bovada = [round(level * (desired_money_in/number_of_intervals), 2) for level in range(number_of_intervals)]
# IT WILL ALWAYS BE NUMBER_OF_INTERVALS + 1 INTERVALS TO COVER FULL RANGE
money_to_bovada.append(desired_money_in)
# calculate range of possibilites to put into predictit based on money_to_bovada
money_to_predictit = [round(desired_money_in-value, 2) for value in money_to_bovada]
# put what I have so far into a pandas dataframe
df = pd.DataFrame(data={'money_to_bovada':money_to_bovada, 'money_to_predictit': money_to_predictit})
# start to calculate to_win values so then profit can be calculated
# first isolate Bovada and PredictIt odds for calculation
# have to get creative for the isolation because both names can be _SIDE_A or _SIDE_B
bovada_odds = arbitrage_dict[next(side for side in arbitrage_dict.keys() if side.split('_')[0] == 'bovada')]
predictit_odds = arbitrage_dict[next(side for side in arbitrage_dict.keys() if side.split('_')[0] == 'predictit')]
# put to_win values in dataframe
df['bovada_to_win'] = ((df['money_to_bovada']/bovada_odds) * (1-bovada_odds)).round(2)
df['predictit_to_win'] = ((df['money_to_predictit']/predictit_odds) * (1-predictit_odds)).round(2)
# use to_win values for profit values (IF THE SIDE ON THAT MARKET WINS)
df['bovada_profit'] = (df['bovada_to_win'] - df['money_to_predictit']).round(2)
df['predictit_profit'] = (df['predictit_to_win'] - df['money_to_bovada']).round(2)
df
# where both bovada_profit and predictit_profit are positive, that is the arbitrage region
df['arbitrage_region'] = np.where((df['bovada_profit'] > 0) & (df['predictit_profit'] > 0), True, False)
# calculate expected profits using calibration_method from above
if calibration_method == 'predictit_only':
df['expected_profit'] = (df['predictit_profit'] * predictit_odds) + (df['bovada_profit'] * (1-predictit_odds))
elif calibration_method == 'bovada_only':
df['expected_profit'] = (df['bovada_profit'] * bovada_odds) + (df['predictit_profit'] * (1-bovada_odds))
else:
combo_odds_bovada = bovada_odds/(bovada_odds + predictit_odds)
combo_odds_predictit = predictit_odds/(bovada_odds + predictit_odds)
df['expected_profit'] = (df['bovada_profit'] * combo_odds_bovada)+(df['predictit_profit'] * combo_odds_predictit)
df['expected_profit'] = df['expected_profit'].round(2)
df_arbitrage = df[df['arbitrage_region']].reset_index()
fig = go.Figure()
# set global_max, global_min, and local_max for graphing boundaries
global_max = max(df['bovada_profit'].max(), df['predictit_profit'].max())
global_min = min(df['bovada_profit'].min(), df['predictit_profit'].min())
local_max = max(df_arbitrage['bovada_profit'].max(), df_arbitrage['predictit_profit'].max())
# set left bound and right bound for graphing boundaries
left_bound = df_arbitrage.iloc[0]['money_to_bovada']
right_bound = df_arbitrage.iloc[-1]['money_to_bovada']
# fill arbitrage region
fig.add_trace(go.Scatter(x=[left_bound, right_bound, right_bound, left_bound],
y=[global_max, global_max, global_min, global_min],
fill='tonexty', fillcolor='lightgreen', mode='none', hoverinfo='none',
name='arbitrage region'))
# draw bovada, predictit, and expected profit lines with pretty text
# bovada
# bovada text list
bovada_text_list = ['Money Into Bovada: $' + str(b) +\
'<br>Money Into PredictIt: $' + str(p) +\
"<br>Profit If Bovada Side Wins: $" + str(profit)\
for b,p, profit\
in zip(df['money_to_bovada'].values,
df['money_to_predictit'].values,
df['bovada_profit'].values)]
# bovada line
fig.add_trace(go.Scatter(x=df['money_to_bovada'],
y=df['bovada_profit'],
name='bovada', marker_color='red', hoverinfo='text', text=bovada_text_list))
# predictit
# predictit text list
predictit_text_list = ['Money Into Bovada: $' + str(b) +\
'<br>Money Into PredictIt: $' + str(p) +\
"<br>Profit If PredictIt Side Wins: $" + str(profit)\
for b,p, profit\
in zip(df['money_to_bovada'].values,
df['money_to_predictit'].values,
df['predictit_profit'].values)]
# predictit line
fig.add_trace(go.Scatter(x=df['money_to_bovada'],
y=df['predictit_profit'],
name='predictit', marker_color='blue', hoverinfo='text', text=predictit_text_list))
# expected profit
# expected profit text list
expected_profit_text_list = ['Money Into Bovada: $' + str(b) +\
'<br>Money Into PredictIt: $' + str(p) +\
"<br>Expected Profit: $" + str(profit)\
for b,p, profit\
in zip(df_arbitrage['money_to_bovada'].values,
df_arbitrage['money_to_predictit'].values,
df_arbitrage['expected_profit'].values)]
# expected profit line
fig.add_trace(go.Scatter(x=df_arbitrage['money_to_bovada'], y=df_arbitrage['expected_profit'],
name='expected profit',
marker_color='green', hoverinfo='text', text=expected_profit_text_list))
# Prefix x-axis tick labels and y-axis tick labels with dollar sign, start with only arbitrage region
fig.update_xaxes(range=[left_bound, right_bound], tickprefix="$")
fig.update_yaxes(range=[0, local_max], tickprefix="$")
# make axis labels
fig.update_layout(
xaxis_title="Money Into Bovada Side",
yaxis_title="Profit")
# show plot with unneccessary buttons removed
fig.show(config={'modeBarButtonsToRemove':['toImage', 'select2d', 'lasso2d', 'autoScale2d',
'toggleSpikelines', 'hoverClosestCartesian', 'hoverCompareCartesian']})